#include "libavutil/avutil.h"
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"

//p19 将视频转为yuv格式
//  ./metedata /Users/wensiyao/Downloads/1111.mp4 1111.yuv
// ffprobe 1111.mp4 查看视频信息
// 播放：ffplay -video_size 720x1280 -pixel_format yuv420p 1111.yuv


int frameCount = 0;

int decodeVideo(AVCodecContext *codecCtx, AVPacket *packet, FILE *dest_fp) {

    int ret = avcodec_send_packet(codecCtx, packet);
    if (ret != 0) {
        av_log(NULL, AV_LOG_ERROR, "avcodec_send_packet failed: %s\n", av_err2str(ret));
        av_packet_unref(packet);
        return  -1;
    }

    AVFrame *frame = av_frame_alloc();
    while (avcodec_receive_frame(codecCtx, frame) == 0) {
        //下边的方法写就可能会出现阶梯型横线。为什么是可能呢。因为如果width正好是64的倍数，那么width=linesize，就不会出现补齐的0x00
        //fwrite(frame->data[0], 1, codecCtx->width * codecCtx->height, dest_fp);
        //fwrite(frame->data[1], 1, codecCtx->width * codecCtx->height / 4, dest_fp);
        //fwrite(frame->data[2], 1, codecCtx->width * codecCtx->height / 4, dest_fp);

        //方法1 可能会出现不行的情况
        //fwrite(frame->data[0], 1, frame->linesize[0] * frame->height, dest_fp);
        //fwrite(frame->data[1], 1, frame->linesize[1] * frame->height / 2, dest_fp);
        //fwrite(frame->data[2], 1, frame->linesize[2] * frame->height / 2, dest_fp);

        //方法2
        frame->height = codecCtx->height;
        frame->width = codecCtx->width;
        int picSize = frame->width * frame->height;
        int newSize = picSize * 1.5;
        unsigned char *buf = (unsigned char *)malloc(newSize * sizeof(unsigned char));
        int a=0,i;
        for (i=0; i<frame->height; i++)
        {
            memcpy(buf+a,frame->data[0] + i * frame->linesize[0], frame->width);
            a+=frame->width;
        }
        for (i=0; i<frame->height/2; i++)
        {
            memcpy(buf+a,frame->data[1] + i * frame->linesize[1], frame->width/2);
            a+=frame->width/2;
        }
        for (i=0; i<frame->height/2; i++)
        {
            memcpy(buf+a,frame->data[2] + i * frame->linesize[2], frame->width/2);
            a+=frame->width/2;
        }

        fwrite(buf, 1, newSize, dest_fp);
        free(buf);
        frameCount++;
        av_log(NULL, AV_LOG_INFO, "frameCount : %d\n", frameCount);
    }
    if (frame) {
        av_frame_free(&frame);
    }
    return 0;
}

int main(int argc, char ** argv) {

    av_log_set_level(AV_LOG_INFO);
    if (argc < 3) {
        av_log(NULL, AV_LOG_ERROR, "Usage: %s <infileName> <outfileName>\n", argv[0]);
        return -1;
    }
    const char *inFileName = argv[1];
    const char *outFileName = argv[2];

    AVFormatContext *inFmtCtx = NULL;
    int ret = avformat_open_input(&inFmtCtx, inFileName, NULL, NULL);
    if (ret != 0) {
        av_log(NULL, AV_LOG_ERROR, "avformat_open_input failed: %s \n",  av_err2str(ret));
        return -1;
    }
    
    ret = avformat_find_stream_info(inFmtCtx, NULL);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "avformat_find_stream_info failed: %s\n", av_err2str(ret));
        goto fail;
    }

    ret = av_find_best_stream(inFmtCtx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR, "av_find_best_stream failed: %s\n", av_err2str(ret));
        goto fail;
    }


    int videoIndex = ret;
    AVCodecContext *codecCtx = avcodec_alloc_context3(NULL);
    if (codecCtx == NULL) {
        av_log(NULL, AV_LOG_ERROR, "avcodec_alloc_context3 failed!\n");
        ret = -1;
        goto fail;
    }

    avcodec_parameters_to_context(codecCtx, inFmtCtx->streams[videoIndex]->codecpar);
    AVCodec * decoder = avcodec_find_decoder(codecCtx->codec_id);
    if (decoder == NULL) {
        av_log(NULL, AV_LOG_ERROR, "avcodec_find_decoder failed:%d\n", codecCtx->codec_id);
        ret = -1;
        goto fail;
    }
    
    ret = avcodec_open2(codecCtx, decoder, NULL);
    if (ret != 0) {
        av_log(NULL, AV_LOG_ERROR, "avcodec_open2 failed: %s\n", av_err2str(ret));
        goto fail;
    }

    FILE *dest_fp = fopen(outFileName, "wb+");
    if (dest_fp == NULL) {
        av_log(NULL, AV_LOG_ERROR, "fopen failed:%s", outFileName);
        ret = -1;
        goto fail;
    }
    AVPacket *packet = av_packet_alloc();
    if (!packet) {
        av_log(NULL, AV_LOG_ERROR, "av_packet_alloc failed\n");
        ret = -1;
        goto fail;
    }


    while (av_read_frame(inFmtCtx, packet) >= 0) {
        if (packet->stream_index == videoIndex) {
            if (decodeVideo(codecCtx, packet, dest_fp) == -1)  {
                ret = -1;
                av_packet_unref(packet);
                goto fail;
            }
        }
        av_packet_unref(packet);
    }

    //把缓存里面都写入 flush decoder 空packet 会去出发刷新
    decodeVideo(codecCtx, packet, dest_fp);



    fail:
    if (inFmtCtx) {
        avformat_close_input(&inFmtCtx);
    }
    if (codecCtx) {
        avcodec_free_context(&codecCtx);
    }

    if (dest_fp) {
        fclose(dest_fp);
    }

    return ret;
}